﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Asteroids
{
    public abstract class CollisionChecker
    {
        //Collision constants
        static readonly double MIN_ACCURACY_SQUARED = Game1.pow2(0.0001);
        const double STEP_SIZE = 0.02f;

        //Most of this function is the result of the thoughts of Thomas Smid, found at http://www.plasmaphysics.org.uk/collision2d.htm.
        public static bool collide(SpaceItem item1, SpaceItem item2, out Vector2 collisionPoint)
        {
            //If items are colliding from the get-go, move them away from each-other, and then shove them (hard) away from each-other.
            if (Game1.pow2(item1.centerPosition.X - item2.centerPosition.X) + Game1.pow2(item1.centerPosition.Y - item2.centerPosition.Y) < Game1.pow2(item1.radius + item2.radius))
            {
                unCollide(item1, item2);
            }
            //Variables to reduce the line length of calculations below.
            Vector2 iPos1 = item1.centerPosition;
            Vector2 iPos2 = item2.centerPosition;
            Vector2 iVel1 = item1.velocity;
            Vector2 iVel2 = item2.velocity;
            //The meat of the collision detection system starts here.
            double d = Math.Sqrt(Game1.pow2(iPos2.X - iPos1.X) + Game1.pow2(iPos2.Y - iPos1.Y));
            double gammaXY = Math.Atan2(iPos2.Y - iPos1.Y,iPos2.X - iPos1.X);
            double gammaV = Math.Atan2(iVel1.Y - iVel2.Y, iVel1.X - iVel2.X);
            if (Math.Abs(d * Math.Sin(gammaXY - gammaV)) > item1.radius + item2.radius)
            {
                move(item1, item2, 1.0);
                item1.pctDistMoved = 1.0f;
                item2.pctDistMoved = 1.0f;
                collisionPoint = Vector2.Zero;
                return false;
            }
            if (Math.Abs(gammaXY - gammaV) > Math.PI / 2.0 && Math.Abs(gammaXY - gammaV) < Math.PI * 1.5)
            {
                move(item1, item2, 1.0);
                item1.pctDistMoved = 1.0f;
                item2.pctDistMoved = 1.0f;
                collisionPoint = Vector2.Zero;
                return false;
            }
            double alpha = Math.Asin(d * Math.Sin(gammaXY - gammaV) / (item1.radius + item2.radius));
            //Find time until collision.
            double firstSq = Math.Sqrt(Game1.pow2(item1.radius + item2.radius) - Game1.pow2(d * Math.Sin(gammaXY - gammaV)));
            if (Math.Cos(gammaXY - gammaV) > 0)
                firstSq = -1 * firstSq;
            double t = (d * Math.Cos(gammaXY - gammaV) + firstSq) / Math.Sqrt(Game1.pow2(iVel1.X - iVel2.X) + Game1.pow2(iVel1.Y - iVel2.Y));
            //Return if items will nto collide this frame.
            if (t > 1.0)
            {
                move(item1, item2, 1.0);
                item1.pctDistMoved = 1.0f;
                item2.pctDistMoved = 1.0f;
                collisionPoint = Vector2.Zero;
                return false;
            }
            //Find positions at point of collision
            Vector2 pos1 = new Vector2((float)(iPos1.X + iVel1.X * t), (float)(iPos1.Y + iVel1.Y * t));
            Vector2 pos2 = new Vector2((float)(iPos2.X + iVel2.X * t), (float)(iPos2.Y + iVel2.Y * t));
            //Start looking for final velocities
            double a = Math.Tan(gammaV + alpha);
            double dv2x = 2 * (iVel1.X - iVel2.X + a * (iVel1.Y - iVel2.Y)) / ((1 + Game1.pow2(a)) * (1 + item2.mass / item1.mass));
            double dv2y = a * dv2x;
            double dv1x = -1 * item2.mass / item1.mass * dv2x;
            double dv1y = -1 * a * item2.mass / item1.mass * dv2x;
            double iDamage1 = 0.5 * item1.mass * (Game1.pow2(dv1x) + Game1.pow2(dv1y));
            double iDamage2 = 0.5 * item2.mass * (Game1.pow2(dv2x) + Game1.pow2(dv2y));
            damageItems(item1, item2, iDamage1, iDamage2);
            item1.velocity += new Vector2((float)dv1x, (float)dv1y);
            item2.velocity += new Vector2((float)dv2x, (float)dv2y);
            //Move items corresponding to detected collisions.
            item1.centerPosition = pos1;
            item2.centerPosition = pos2;
            move(item1, item2, 1.0 - t);
            item1.pctDistMoved = 1.0f;
            item2.pctDistMoved = 1.0f;
            collisionPoint = Vector2.Zero;
            return true;
        }

        private static void damageItems(SpaceItem item1, SpaceItem item2, double iDamage1, double iDamage2)
        {
            //Okay it's back?
            if (item1 is Enemy)
            {
                if (item2 is Asteroid && ((Asteroid)item2).isBullet && (Math.Abs(item2.velocity.X) + Math.Abs(item2.velocity.Y) > 4))
                    item1.health--;
                else if (iDamage1 > 4 && !(item2 is Player))
                    item1.health -= (int)iDamage1;
                else if (item2 is Player)
                    item2.health -= 10;
            }
            else if (item1 is Asteroid)
            {
                if (iDamage1 > 4)
                    item1.health--;
            }
            
            if (item2 is Enemy)
            {
                if (item1 is Asteroid && ((Asteroid)item1).isBullet && (Math.Abs(item1.velocity.X) + Math.Abs(item1.velocity.Y) > 4))
                    item2.health--;
                else if(iDamage1 > 4 && !(item1 is Player))
                    item2.health -= (int)iDamage2;
                else if (item1 is Player)
                    item1.health -= 10;
            }
            else if (item2 is Asteroid)
            {
                if (iDamage2 > 4)
                    item2.health--;
            }           
        }

        private static void move(SpaceItem item1, SpaceItem item2, double time)
        {
            if(item1.pctDistMoved < 1.0f)
                item1.centerPosition += new Vector2(item1.velocity.X * (float)time, item1.velocity.Y * (float)time);
            if (item2.pctDistMoved < 1.0f)
                item2.centerPosition += new Vector2(item2.velocity.X * (float)time, item2.velocity.Y * (float)time);
        }

        private static void unCollide(SpaceItem item1, SpaceItem item2)
        {
            double dx = item1.centerPosition.X - item2.centerPosition.X;
            double dy = item1.centerPosition.Y - item2.centerPosition.Y;
            double dist = Math.Sqrt(Game1.pow2(dx) + Game1.pow2(dy));
            double minDist = item1.radius + item2.radius;
            //moveDist will never be negative because this function is only called when we already know the two objects overlap.
            double moveDist = minDist - dist;
            double pctToMove = moveDist / dist;
            //divide the pctToMove by a number less than two so that the objects have some headroom to collide again if they so choose.
            item1.centerPosition += new Vector2((float)(dx * pctToMove) / 1.5f, (float)(dy * pctToMove) / 1.5f);
            item2.centerPosition -= new Vector2((float)(dx * pctToMove) / 1.5f, (float)(dy * pctToMove) / 1.5f);
            item1.velocity += new Vector2((float)(dx * pctToMove), (float)(dy * pctToMove));
            item2.velocity -= new Vector2((float)(dx * pctToMove), (float)(dy * pctToMove));
        }
    }
}
